﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using System.Reflection;
using System.Collections;
using System.Runtime.Serialization;
using System.Xml.Serialization;

namespace FerrSimplicity
{
    public abstract class BaseComponent : ICloneable
    {
        #region Properties
        /// <summary>
        /// The parent GameObject, get only
        /// </summary>
        [XmlIgnore]
        public BaseGameObject Parent { get; internal set; }
        #endregion

        #region Events
        /// <summary>
        /// This method is called by BaseGameObject when the GameObject receives a LoadContent call 
        /// </summary>
        public virtual void LoadContent  () { }
        /// <summary>
        /// This method is called by BaseGameObject when the GameObject receives a UnloadContent call 
        /// </summary>
        public virtual void UnloadContent() { }
        /// <summary>
        /// This method is called by BaseGameObject when the GameObject receives an Update call 
        /// </summary>
        public virtual void Update(GameTime aGameTime) { }
        /// <summary>
        /// This method is called by BaseGameObject when the GameObject receives a Draw call 
        /// </summary>
        public virtual void Draw  (GameTime aGameTime) { }
        /// <summary>
        /// This gets called when the parent BaseGameObject is enabled or disabled, note, this does
        /// NOT disable this particular component!
        /// </summary>
        /// <param name="aEnableValue">The value of the parent BaseGameObjects Enabled property</param>
        public virtual void Enable(bool aEnableValue) { }
        #endregion

        #region Methods
        /// <summary>
        /// This method should be called on LoadContent, or on Enable(true). If the indicated
        /// component type is not in the parent's component list, this method will throw an
        /// exception.
        /// </summary>
        /// <typeparam name="T">The class type or base class type that should be required</typeparam>
        protected void RequireComponent<T>() where T : BaseComponent
        {
            if (Parent.GetComponent<T>() != null)
                return;
            else
                throw new Exception("" + this.GetType().Name + " requires that a component of type " + typeof(T).Name + " be in the parent's component list.");
        }
        #endregion

        #region ICloneable
        public virtual object Clone()
        {
            object newObject = null;

            try
            { newObject = Activator.CreateInstance(this.GetType()); }
            catch (MissingMethodException e)
            { throw new Exception("There are no parameterless constructors for this component type! <" + this.GetType().Name + "> \n" + e.Message); }
            catch (MemberAccessException e)
            { throw new Exception("Please make sure that the constructor of " + this.GetType().Name + "and class itself are public.\n" + e.Message); }

            CopyFields    (newObject);
            CopyProperties(newObject);
            return newObject;
        }

        private void CopyFields    (object aNewObject)
        {
            FieldInfo[] fields = aNewObject.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public/* | BindingFlags.NonPublic*/ | BindingFlags.FlattenHierarchy);
            int i = 0;

            foreach (FieldInfo fi in this.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public/* | BindingFlags.NonPublic*/ | BindingFlags.FlattenHierarchy))
            {
                object value = fi.GetValue(this);

                // if we can just clone it, awesome! do it!
                if (value is ICloneable)
                    fields[i].SetValue(aNewObject, ((ICloneable)value).Clone());
                else
                    fields[i].SetValue(aNewObject, value);

                // if it's a list of some sort, it needs some special treatment.
                if (fi.GetValue(this) is IEnumerable)
                {
                    IEnumerable IEnum = (IEnumerable)value;

                    int j = 0;
                    if (IEnum is IList)
                    {
                        IList list = (IList)fields[i].GetValue(aNewObject);

                        foreach (object obj in IEnum)
                        {
                            if (obj is ICloneable)
                                list[j] = ((ICloneable)obj).Clone();
                            else
                                list[j] = obj;

                            j++;
                        }
                    }
                    else if (IEnum is IDictionary)
                    {
                        // just going to assume that dictionaries can be done as reference
                        fields[i].SetValue(aNewObject, IEnum);
                    }
                }
                i++;
            }
        }
        private void CopyProperties(object aNewObject)
        {
            PropertyInfo[] properties = aNewObject.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public/* | BindingFlags.NonPublic*/ | BindingFlags.FlattenHierarchy);
            int i = 0;

            foreach (PropertyInfo pi in       this.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public/* | BindingFlags.NonPublic*/ | BindingFlags.FlattenHierarchy))
            {
                // only do this if the property has both a get and a set component
                if (!(pi.CanRead && pi.CanWrite && properties[i].CanRead && properties[i].CanWrite) || pi.PropertyType.IsAssignableFrom(typeof(BaseGameObject)))
                {
                    i++;
                    continue;
                }

                object value = pi.GetValue(this, null);

                // if we can just clone it, awesome! do it!
                try
                {
                    if (value is ICloneable)
                        properties[i].SetValue(aNewObject, ((ICloneable)value).Clone(), null);
                    else
                        properties[i].SetValue(aNewObject, value, null);
                }
                catch (MethodAccessException e)
                {
                    throw new Exception("Compact framework trust does not permit properties that contain private or protected 'set's.\n" + e.Message);
                }
                catch (TargetInvocationException e)
                {
                    throw new Exception("An exception was thrown while trying to set property '" + properties[i].Name + "' in '" + aNewObject.GetType().Name + "' with value '" + value + "'\n" + e.Message + "\n"+ e.InnerException.Message);
                }

                // if it's a list of some sort, it needs some special treatment.
                if (value is IEnumerable)
                {
                    IEnumerable IEnum = (IEnumerable)value;

                    int j = 0;
                    if (IEnum is IList)
                    {
                        IList list = (IList)properties[i].GetValue(aNewObject, null);

                        foreach (object obj in IEnum)
                        {
                            if (obj is ICloneable)
                                list[j] = ((ICloneable)obj).Clone();
                            else
                                list[j] = obj;

                            j++;
                        }
                    }
                    else if (IEnum is IDictionary)
                    {
                        // just going to assume that dictionaries can be done as reference
                        properties[i].SetValue(aNewObject, IEnum, null);
                    }
                }
                i++;
            }
        }
        #endregion
    }
}
